/* 
 * Autores:
 *      Jonathan Lopez  11106
 *      Jorge Martínez  11237
 * 
 * Archivo: Puerto.java
 * Última Revision: $Rev: 69 $
 *      Fecha: $Date: 2011-09-19 06:58:38 +0000 (Mon, 19 Sep 2011) $
 */

package Manejo.Transporte;

import Manejo.Transporte.Registros.*;
import Archivador.*;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;

/**
 * Representa un Puerto al que pueden llegar Barcos, los cuales pueden
 * cargar y descargar Contenedores en estos lugares.
 * 
 * @author Jonathan López
 * @author Jorge Martinez
 */
public class Puerto implements iArchivable
{
    private ArrayList<ID> barcosAtracados;
    private Localizacion posicion;
    private ArrayList<ID> contenedoresPorCargar;
    private ArrayList<ID> contenedoresDescargados;
    
    private RegBarcos registroBarcos;
    private RegPuertos registroPuertos;
    private RegRutas registroRutas;
    private RegContenedores registroContenedores;
    
    private final Registros registros;
    
    /**
     * Constructor para Puerto
     * @param posicion Representa la localización en el mundo en la que se 
     * encuentra el Puerto
     * @param registroBarcos
     * @param registroPuertos
     */
    public Puerto( Localizacion posicion, Registros registros ){
        this.posicion = posicion;
        this.registroBarcos = registros.obtenerRegistroBarcos();
        this.registroPuertos = registros.obtenerRegistroPuertos();
        this.registroRutas = registros.obtenerRegistroRutas();
        this.registroContenedores = registros.obtenerRegistroContenedores();
        this.registros = registros;
        
        barcosAtracados = new ArrayList<ID>();
        contenedoresPorCargar = new ArrayList<ID>();
        contenedoresDescargados = new ArrayList<ID>();
    }
    
    /**
     * Indica que un Barco va a atracar en el Puerto. El Barco tiene que estar
     * registrado en el registro de Barcos. Éste es automáticamente descargado
     * y cargado.
     * @param idBarco ID del Barco que atracará en el Puerto
     * @return true si la operación fue exitosa, false en caso contrario
     */
    public boolean atracarBarco( ID idBarco ){
        // Revisar que el barco no este ya en el puerto
        if( barcosAtracados.contains( idBarco ) )
            return false;
        
        // Revisar que el barco sea valido
        Barco barcoActual = registroBarcos.obtenerBarco(idBarco);
        if( barcoActual == null )
            return false;
        
        // Obtener el ID propio
        ID idPropio = registroPuertos.obtenerID(this);
        if( idPropio.equals(ID.idNulo) )
            return false;
        
        
        barcosAtracados.add(idBarco);
        barcoActual.asignarPuerto(idPropio);
        
        // Descargar y cargar el Barco
        recibirContenedores( idBarco );
        entregarContenedores( idBarco );
        return true;
    }
    
    /**
     * Retira a un Barco del Puerto. El Barco tiene que haber sido atracado
     * antes en el Puerto para que pueda zarpar.
     * @param idBarco ID del Barco a zarpar.
     * @return true si la operación fue exitosa, false en caso contrario
     * @see Puerto#atracarBarco(Manejo.Transporte.Registros.ID) 
     */
    public boolean zarparBarco(ID idBarco){
        // Revisar que el barco exista en el puerto
        if( !barcosAtracados.contains(idBarco) )
            return false;
        
        // Revisar que el barco sea valido
        Barco barcoActual = registroBarcos.obtenerBarco(idBarco);
        if( barcoActual == null )
            return false;
        
        // Obtener el ID propio
        ID idPropio = registroPuertos.obtenerID(this);
        if( idPropio.equals(ID.idNulo) )
            return false;
        
        barcosAtracados.remove(idBarco);
        barcoActual.asignarPuerto( ID.idNulo );
        return true;
    }
    
    /**
     * Devuelve si el Barco dado se encuentra atracado en el Puerto o no.
     * @param idBarco ID del Barco a comprobar
     * @return true si el Barco está atracado, false en caso contrario.
     */
    public boolean estaBarcoAtracado( ID idBarco ){
        return barcosAtracados.contains( idBarco );
    }

    /**
     * Devuelve la posición en que se encuentra el Puerto
     * @return Localización con la posición
     */
    public Localizacion obtenerPosicion()
    {
        return posicion;
    }
    
    /**
     * Agrega un Contenedor al listado de contenedores por cargar, a manera
     * que cuando pase un Barco que pase por el Puerto de destino del Contenedor
     * se lo lleve.
     * @param idContenedor ID del Contenedor
     */
    public void agregarContenedor( ID idContenedor ){
        Contenedor nuevoContenedor = registroContenedores.obtenerContenedor(idContenedor);
        if( nuevoContenedor == null )
            return;
        
        ID idEstePuerto = registroPuertos.obtenerID(this);
        if( nuevoContenedor.obtenerPuertoDestino().equals(idEstePuerto) )
            contenedoresDescargados.add(idContenedor);
        else
            contenedoresPorCargar.add(idContenedor);
    }
    
    /**
     * Entrega los contenedores que son para el Barco. Esto lo logra
     * revisando si el Puerto destino del Contenedor se encuentra entre los
     * Puertos de Viaje del Barco.
     * @param idBarco identificador del barco que descarga
     */
    private void entregarContenedores( ID idBarco ){
        // Valida el Barco y obtiene sus Puertos destino
        if( idBarco.equals( ID.idNulo ) )
            return;
        Barco barcoActual = registroBarcos.obtenerBarco(idBarco);
        if( barcoActual == null )
            return;
        
        Viaje viajeBarco = barcoActual.obtenerViaje();
        ID idRutaBarco = viajeBarco.obtenerRuta();
        Ruta rutaBarco = registroRutas.obtenerRuta(idRutaBarco);
        if( rutaBarco == null )
            return;
        
        ID[] puertosDestino = rutaBarco.obtenerPuertos();
        int viajePuertoActual = viajeBarco.obtenerIndicePuertoActual();
        
        
        // Revisa con cada contenedor si el Puerto destino se encuentra entre
        // los destinos del Barco
        Iterator<ID> it = contenedoresPorCargar.iterator();
        while( it.hasNext() ){
            ID idContenedorActual = it.next();
            Contenedor contenedorActual = 
                    registroContenedores.obtenerContenedor( idContenedorActual );
            if( contenedorActual == null )
                continue;
            
            for (int indicePuerto = viajePuertoActual+1;
                    indicePuerto < puertosDestino.length;
                    indicePuerto++) {
                ID idPuerto = puertosDestino[indicePuerto];
                if( contenedorActual.obtenerPuertoDestino().equals(idPuerto) ){
                    if( barcoActual.cargar( idContenedorActual ) ){
                        // Remover el Contenedor que se cargó en el Barco
                        it.remove();
                        break;
                    } else
                        // Cuando ya no hay espacio entonces retornar la funcion
                        return;
                }
            }
            
            
        }
    }
    
    /**
     * Recibe los contnedores para este puerto
     * @param idBarco
     * @return  
     */
    private boolean recibirContenedores( ID idBarco ){
        // Revisar que el barco exista en el puerto
        if( !barcosAtracados.contains(idBarco) )
            return true;
        
        // Revisar que el barco sea valido
        Barco barcoActual = registroBarcos.obtenerBarco(idBarco);
        if( barcoActual == null )
            return false;
        
        ID[] nuevosContenedores = barcoActual.descargar();
        contenedoresDescargados.addAll(Arrays.asList(nuevosContenedores));
        return true;
    }

    /**
     * @return Devuelve un arreglo con todos los Contenedores recibidos en 
     * este Puerto.
     */
    public ID[] obtenerContenedoresRecibidos(){
        return contenedoresDescargados.toArray( new ID[0] );
    }
    
    /**
     * Regresa una representación String del Puerto con barcos atracados,
     * posición y los contenedores por cargar y descargar
     * @return representación String
     */
    @Override
    public String toString()
    {
        // Obtener los nombres de los Barcos
        String[] nombreBarcos = new String[barcosAtracados.size()];
        for (int n = 0; n < barcosAtracados.size(); n++) {
            ID idBarco = barcosAtracados.get(n);
            nombreBarcos[n] = registroBarcos.obtenerNombre(idBarco);
        }
        String sBarcos = "";
        if( nombreBarcos.length != 0 )
            sBarcos = ", barcosAtracados=" + Arrays.toString(nombreBarcos);
        
        // Contenedores
        /* Por cargar */
        String sCargar = "";
        if( !contenedoresPorCargar.isEmpty() )
            sCargar = ", Por cargar=" + contenedoresPorCargar;
        /* Por descargar */
        String sDescargados = "";
        if( !contenedoresDescargados.isEmpty() )
            sDescargados = ", Descargados" + contenedoresDescargados;
        
        return "Puerto{" + posicion +
                sBarcos + sCargar + sDescargados + '}';
    }
    
    
    
    /**
     * Regresa un línea de texto para ser guardada en un medio persistente.
     * @return String
     * @see iArchivable#guardar() 
     */
    @Override
    public String guardar(){
        // Orden: [Localizacion]##[Barcos]##[CARGAR]##[Contenedor...]##
        // [DESCARGADOS]##[Contenedor...]
        
        String linea = "";
        linea += posicion.guardar() + separadorObjetos;
        
        /* Agregar barcos atracados*/
        linea += "[";
        for( ID idBarco: barcosAtracados )
            linea += idBarco.guardar() + separador;
        linea += "]" + separadorObjetos;
        
        /* Agregar contenedores por cargar */
        linea += "[CARGAR]" + separadorObjetos + "[";
        for( ID idContenedor: contenedoresPorCargar )
            linea += idContenedor.guardar() + separador;
        linea += "]" + separadorObjetos;
        
        /* Agregar contendores por descargar */
        linea += "[DESCARGAR]" + separadorObjetos + "[";
        for( ID idContenedor: contenedoresDescargados )
            linea += idContenedor.guardar() + separador;
        linea += "]";
        
        return linea;
    }
    
    /**
     * Genera un nuevo Puerto a partir de una línea de texto creada a través
     * del método guardar()
     * @param linea
     * @param registroBarcos
     * @param registroPuertos
     * @param registroRutas
     * @param registroContenedores
     * @return nuevo Puerto a partir del String, o null si falla
     */
    public static Puerto parsePuerto( String linea, Registros registros ){
        // Separar los objetos y validar
        int n = 0;  // Objeto actual
        int totalObjetos = 6;
        String[] objetos = linea.split( separadorObjetos );
        if( objetos.length != totalObjetos )
            return null;
        
        // Validar cada uno de los objetos
        for( String obj: objetos )
            if( !Util.validarLinea(obj) )
                return null;
        
        // ** LOCALIZACION **
        Localizacion _posicion = Localizacion.parseLocalizacion(objetos[n++]);
        if( _posicion == null )
            return null;
        
        // ** BARCOS **
        ArrayList<ID> _barcosAtracados = new ArrayList<ID>();
        String srBarcos = Util.eliminarExtremos(objetos[n++]);
        String[] partesBarcos = srBarcos.split( separador );
        for( String barco: partesBarcos ){
            if( !barco.equals("") ){
                ID idBarco = ID.parseID(barco);
                if( idBarco.equals( ID.idNulo ) )
                    // DEBUG
                    System.out.println("*****ID MALO*****");
                else
                    _barcosAtracados.add(idBarco);
            }
        }
        
        // ** CARGAR **
        if( !objetos[n++].equals( "[CARGAR]" ) )
            return null;
        
        ArrayList<ID> _contenedoresPorCargar = new ArrayList<ID>();
        String porCargar = objetos[n++];
        porCargar = Util.eliminarExtremos( porCargar );
        for( String cargar: porCargar.split(separador) ){
            if( !cargar.equals("") ){
                ID idContenedor = ID.parseID( cargar );
                if( idContenedor.equals(ID.idNulo) )
                    // DEBUG
                    System.out.println("**ID MALO - CARGAR**");
                else
                    _contenedoresPorCargar.add(idContenedor);
            }
        }
        
        // ** DESCARGAR **
        if( !objetos[n++].equals( "[DESCARGAR]" ))
            return null;
        
        ArrayList<ID> _contenedoresDescargados = new ArrayList<ID>();
        String porDescargar = objetos[n++];
        porDescargar = Util.eliminarExtremos(porDescargar);
        
        for( String descargar: porDescargar.split(separador) ){
            if( !descargar.equals("") ){
                ID idContenedor = ID.parseID( descargar );
                if( idContenedor.equals( ID.idNulo) )
                    // DEBUG
                    System.out.println("**ID MALO - DESCARGAR**");
                else
                    _contenedoresDescargados.add(idContenedor);
            }
        }
        
        // Crear nuevo puerto
        Puerto nuevoPuerto = new Puerto( _posicion, registros );
        nuevoPuerto.barcosAtracados = _barcosAtracados;
        nuevoPuerto.contenedoresPorCargar = _contenedoresPorCargar;
        nuevoPuerto.contenedoresDescargados = _contenedoresDescargados;
        
        return nuevoPuerto;
    }
    
    /**
     * Punto de entrada para las pruebas unitarias de Puerto
     * @param args
     */
    public static void main( String[] args ){
        int pruebas = 10;
        int fallos = 0;
        java.util.Random gen = new java.util.Random();
        Registros registros = new Registros();
        
        RegPuertos registroPuertos = registros.obtenerRegistroPuertos();
        
        for (int n = 0; n != pruebas; n++) {
            Localizacion pos = new Localizacion( "Centroamérica y Caribe",
                    gen.nextDouble(), gen.nextDouble() );
            Puerto puerto = new Puerto( pos, registros );
            
            /* Agregar barcos */
            int nBarcos = gen.nextInt(8);
            ArrayList<ID> barcos = new ArrayList<ID>();
            for (int i = 0; i != nBarcos; i++)
                barcos.add(GeneradorID.nuevoID());
            puerto.barcosAtracados = barcos;
            
            /* Agregar contenedores cargados */
            int nCargar = gen.nextInt(10);
            ArrayList<ID> cargar = new ArrayList<ID>();
            for (int i = 0; i != nCargar; i++)
                cargar.add(GeneradorID.nuevoID());
            puerto.contenedoresPorCargar = cargar;
            
            /* Agregar contenedores descargados */
            int nDescargar = gen.nextInt(10);
            ArrayList<ID> descargar = new ArrayList<ID>();
            for (int i = 0; i != nDescargar; i++)
                descargar.add(GeneradorID.nuevoID());
            puerto.contenedoresDescargados = descargar;
            
            String linea = puerto.guardar();
            Puerto nuevoPuerto = Puerto.parsePuerto(linea, registros );
            
            boolean iguales = puerto.toString().equals(nuevoPuerto.toString());
            if( !iguales )
                ++fallos;
            
            System.out.println( linea + " & " + iguales );
            System.out.println( "\t" + puerto );
            System.out.println( "\t" + nuevoPuerto );
            
            // Agregar el puerto al registro
            registroPuertos.agregarPuerto( puerto, Integer.toHexString(gen.nextInt()));
        }
        
        System.out.println("FALLOS: " + fallos );
        
        registroPuertos.guardarTodo();
        Informador.guardarCambios();
    }
}
